/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.sensor.model.impl;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import nl.cloudfarming.client.sensor.SensorService;
import nl.cloudfarming.sensor.model.GeoSensorDataBO;
import nl.cloudfarming.sensor.model.GeoSensorDataFileBO;
import nl.cloudfarming.sensor.model.SensorMetadataBO;
import nl.cloudfarming.sensor.model.SensorRunBO;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openide.util.lookup.ServiceProvider;
import javax.persistence.EntityManager;
import nl.cloudfarming.sensor.model.PlotBO;
import nl.cloudfarming.sensor.model.PlotDataFileBO;

/**
 * @author I. de Vries
 */
@ServiceProvider(service = SensorService.class)
public class SensorServiceImpl implements SensorService {

    private Log log = LogFactory.getLog(SensorServiceImpl.class);

    public SensorServiceImpl() {
//        EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("nl-cloudfarming-sensor-cropcircle");
//        EntityManager em = emFactory.createEntityManager();
//        setEntityManager(em);
        //setEntityManager(EntityManagerContainer.getInstance().getEntityManager());
        //EntityManager entityManager = JPASessionLocator.getEntityManager();
    }

    private void startTransaction() {
        if (!getEntityManager().getTransaction().isActive()) {
            getEntityManager().getTransaction().begin();
        }
    }

    public EntityManager getEntityManager() {
        return JPASessionLocator.getEntityManager();
    }

    @Override
    public void persist(GeoSensorDataBO geoSensorDataBO) {
        if (geoSensorDataBO.getGeoDataSensorFile() == null) {
            throw new IllegalArgumentException("Cannot persist a geoSensorDataBO without a GeoDataSensorFile");
        }
        startTransaction();
        getEntityManager().persist(geoSensorDataBO);
        //getEntityManager().flush();

        getEntityManager().getTransaction().commit();
    }

    @Override
    public void persist(GeoSensorDataFileBO geoSensorDataFileBO) {
        startTransaction();
        mergeGeoSensorDataFile(geoSensorDataFileBO);
        //getEntityManager().flush();
        getEntityManager().getTransaction().commit();
    }

    @Override
    public void persistGeoSensorDataBOList(List<GeoSensorDataBO> unit) {
        startTransaction();
        for (GeoSensorDataBO u : unit) {
            getEntityManager().persist(u);
        }
        getEntityManager().getTransaction().commit();
    }

    @Override
    public void persistPlotBOList(List<PlotBO> unit) {
        startTransaction();
        for (PlotBO u : unit) {
            getEntityManager().persist(u);
        }
        //getEntityManager().flush();
        getEntityManager().getTransaction().commit();
    }

    @Override
    public void persist(SensorMetadataBO sensorMetaDataBO) {
        startTransaction();
        mergeSensorMetadata(sensorMetaDataBO);
        //getEntityManager().flush();
        getEntityManager().getTransaction().commit();
    }

    @Override
    public void persist(PlotBO plotBO) {
        startTransaction();
        getEntityManager().persist(plotBO);
        getEntityManager().getTransaction().commit();
    }

    @Override
    public void persist(PlotDataFileBO plotDataFileBO) {
        startTransaction();
        mergePlotDataFile(plotDataFileBO);
        getEntityManager().getTransaction().commit();
    }

    @Override
    public void persist(SensorRunBO sensorRunBO) {
        startTransaction();
        mergeSensorRunBO(sensorRunBO);
        getEntityManager().getTransaction().commit();
    }

    protected void delete(GeoSensorDataBO geoSensorDataBO) {
        getEntityManager().remove(geoSensorDataBO);
    }

    protected void delete(GeoSensorDataFileBO geoSensorDataFileBO) {
        getEntityManager().remove(geoSensorDataFileBO);
    }

    protected void delete(SensorMetadataBO sensorMetaDataBO) {
        getEntityManager().remove(sensorMetaDataBO);
    }

    protected void delete(PlotBO plotBO) {
        getEntityManager().remove(plotBO);
    }

    protected void delete(PlotDataFileBO plotDataFileBO) {
        getEntityManager().remove(plotDataFileBO);
    }

    protected void delete(SensorRunBO sensorRunBO) {
        getEntityManager().remove(sensorRunBO);
    }

    @Override
    public void updateSensorMetadata(GeoSensorDataFileBO sensorDataFileBO, SensorMetadataBO newSensorMetadataBO) {
        startTransaction();
        if (sensorDataFileBO.getGeoSensorDataFileId() == 0) {
            throw new IllegalArgumentException("Cannot update a GeoSensorDataFileBO if it does not exist");
        } else {
            if (newSensorMetadataBO.getSensorMetadataId() != 0) {
                sensorDataFileBO.setSensorMetadata(newSensorMetadataBO);
            } else {
                SensorMetadataBO existing = findSensorMetaDataBOByMetadata(newSensorMetadataBO.getDateFormat(), newSensorMetadataBO.getSensorHeight());
                if (existing != null) {
                    sensorDataFileBO.setSensorMetadata(existing);
                    sensorDataFileBO = getEntityManager().merge(sensorDataFileBO);
                } else {
                    getEntityManager().persist(newSensorMetadataBO);
                    sensorDataFileBO.setSensorMetadata(newSensorMetadataBO);
                }
            }
            getEntityManager().getTransaction().commit();
        }
    }

    private void mergeGeoSensorDataFile(GeoSensorDataFileBO toBeMerged) {
        if (toBeMerged.getGeoSensorDataFileId() == 0) {
            getEntityManager().persist(toBeMerged);
        } else {
            toBeMerged = getEntityManager().merge(toBeMerged);
        }
    }

    private void mergeSensorMetadata(SensorMetadataBO toBeMerged) {
        SensorMetadataBO existing;
        //managed
        if (toBeMerged.getSensorMetadataId() != 0) {
            getEntityManager().merge(toBeMerged);
        } // Not managed but existing in db
        else if ((existing = findSensorMetaDataBOByMetadata(toBeMerged.getDateFormat(), toBeMerged.getSensorHeight())) != null) {
            toBeMerged.setSensorMetadataId(existing.getSensorMetadataId());
            getEntityManager().merge(toBeMerged);
        } //New item
        else {
            getEntityManager().persist(toBeMerged);
        }

    }

    private void mergePlotDataFile(PlotDataFileBO toBeMerged) {
        if (toBeMerged.getDataFileId() == 0) {
            getEntityManager().persist(toBeMerged);
        } else {
            toBeMerged = getEntityManager().merge(toBeMerged);
        }
    }

    private void mergeSensorRunBO(SensorRunBO toBeMerged) {
        if (toBeMerged.getSensorRunId() == 0) {
            getEntityManager().persist(toBeMerged);
        } else {
            toBeMerged = getEntityManager().merge(toBeMerged);
        }
    }

    public PlotBO newInstance(PlotDataFileBO plotDataFileBO, String name, String WKT) {
        PlotBO plotBO = new PlotBO(plotDataFileBO, name, WKT);
        persist(plotBO);
        return plotBO;
    }

//    public SensorMetadataBO alterSensorMetadata(SensorMetadataBO sensorMetadataBO){
//        findSensorMetaDataBOById(id)
//    }
    @Override
    public GeoSensorDataBO findGeoSensorDataBOById(long id) {
        startTransaction();
        return getEntityManager().find(GeoSensorDataBO.class, id);
    }

    @Override
    public SensorMetadataBO findSensorMetaDataBOById(long id) {
        startTransaction();
        return getEntityManager().find(SensorMetadataBO.class, id);
    }

    public PlotBO findById(long id) {
        startTransaction();
        return getEntityManager().find(PlotBO.class, id);
    }

    @Override
    public List<GeoSensorDataFileBO> findAllGeoSensorDataFiles() {
        startTransaction();
        Query query = this.getEntityManager().createQuery("select g from GeoSensorDataFileBO g");
        return query.getResultList();
    }

    @Override
    public List<PlotDataFileBO> findAllPlotDataFiles() {
        startTransaction();
        Query query = this.getEntityManager().createQuery("select p from PlotDataFileBO p");
        return query.getResultList();
    }

    @Override
    public List<GeoSensorDataBO> findAllGeoSensorData() {
        startTransaction();
        Query query = getEntityManager().createQuery("select g from GeoSensorDataBO g");
        return query.getResultList();
    }

    @Override
    public List<GeoSensorDataBO> findAllGeoSensorDataNoSensorRun() {
        startTransaction();
        Query query = getEntityManager().createQuery("select g from GeoSensorDataBO g where g.sensorRun is null");
        return query.getResultList();
    }

    @Override
    public List<SensorMetadataBO> findAllSensorMetaData() {
        startTransaction();
        Query query = getEntityManager().createQuery("select s from SensorMetadataBO s");
        return query.getResultList();
    }

    @Override
    public List<PlotBO> findAllPlotData() {
        startTransaction();
        Query query = getEntityManager().createQuery("select p from PlotBO p");
        return query.getResultList();
    }

    @Override
    public GeoSensorDataFileBO findGeoSensorDataFileByNameChecksum(String fileName, String checksum) {
        startTransaction();
        Query query = getEntityManager().createQuery(
                "select g from GeoSensorDataFileBO g "
                + "where g.fileName = :fileName "
                + "and g.checksum = :checksum");
        query.setParameter("fileName", fileName);
        query.setParameter("checksum", checksum);
        try {
            return (GeoSensorDataFileBO) query.getSingleResult();
        } catch (NoResultException ner) {
            return null;
        }
    }

    @Override
    public PlotDataFileBO findPlotDataFileByNameChecksum(String fileName, String checksum) {
        startTransaction();
        Query query = getEntityManager().createQuery(
                "select p from PlotDataFileBO p "
                + "where p.fileName = :fileName "
                + "and p.checksum = :checksum");
        query.setParameter("fileName", fileName);
        query.setParameter("checksum", checksum);
        try {
            return (PlotDataFileBO) query.getSingleResult();
        } catch (NoResultException ner) {
            return null;
        }
    }

    @Override
    public SensorMetadataBO findSensorMetaDataBOByMetadata(String dateFormat, int sensorHeight) {
        startTransaction();
        Query query = getEntityManager().createQuery(
                "select s from SensorMetadataBO s "
                + "where s.dateFormat = :dateFormat "
                + "and s.sensorHeight = :sensorHeight");
        query.setParameter("dateFormat", dateFormat);
        query.setParameter("sensorHeight", sensorHeight);
        try {
            return (SensorMetadataBO) query.getSingleResult();
        } catch (NoResultException ner) {
            return null;
        }
    }

    @Override
    public List<SensorRunBO> findAllSensorRuns() {
        startTransaction();
        Query query = getEntityManager().createQuery("select s from SensorRunBO s ");
        return query.getResultList();
    }

    @Override
    public List<SensorRunBO> findSensorRunsByPeriod(Date dateFrom, Date dateUntil) {
        startTransaction();
        Query query = getEntityManager().createQuery(
                "select s from SensorRunBO s "
                + "where s.geoSensorDataFileBO.fileCreated > :dateFrom "
                + "and s.geoSensorDataFileBO.fileCreated < dateUntil ");
        query.setParameter("dateFormat", dateFrom);
        query.setParameter("dateUntil", dateUntil);
        return query.getResultList();
    }

    @Override
    public SensorRunBO findSensorRunBO(GeoSensorDataFileBO geoDataSensorFile, PlotBO bo) {
        startTransaction();
        Query query = getEntityManager().createQuery(
                "select s from SensorRunBO s "
                + "where s.geoSensorDataFileBO = :geoDataSensorFile "
                + "and s.plotBO = :plot ");
        query.setParameter("geoDataSensorFile", geoDataSensorFile);
        query.setParameter("plot", bo);
        try {
            return (SensorRunBO) query.getSingleResult();
        } catch (NoResultException ner) {
            return null;
        }
    }

    @Override
    public List<GeoSensorDataBO> findSensorDataForRun(SensorRunBO sensorRunBO) {
        startTransaction();
        Query query = getEntityManager().createQuery(
                "select s from GeoSensorDataBO s "
                + "where s.sensorRun = :sensorRunBO ");
        query.setParameter("sensorRunBO", sensorRunBO);
        return query.getResultList();
    }

    @Override
    public List<SensorRunBO> findSensorRunsForPlot(PlotBO plotBO) {
        startTransaction();
        Query query = getEntityManager().createQuery(
                "select s from SensorRunBO s "
                + "where s.plotBO = :plotBO ");
        query.setParameter("plotBO", plotBO);
        return query.getResultList();
    }

    @Override
    public List<PlotBO> findPlotsForYear(int year) throws ParseException {
        startTransaction();
        DateFormat aDateFormat = new SimpleDateFormat("ddMMyyyy");
        Date dateFrom = aDateFormat.parse("0101" + year);
        Date dateUntil = aDateFormat.parse("0101" + (year + 1));

        Query query = getEntityManager().createQuery(
                "select distinct p from SensorRunBO s, PlotBO p "
                + "where s.geoSensorDataFileBO.fileCreated >= :dateFrom "
                + "and s.geoSensorDataFileBO.fileCreated < :dateUntil "
                + "and s.plotBO = p ");
        query.setParameter("dateFrom", dateFrom);
        query.setParameter("dateUntil", dateUntil);
        return query.getResultList();
    }

    @Override
    public void bulkPersist(List<GeoSensorDataBO> sensorDataList) {
        startTransaction();
        for (GeoSensorDataBO bo : sensorDataList) {
            getEntityManager().persist(bo);
        }
        getEntityManager().getTransaction().commit();
    }

    @Override
    public List<SensorRunBO> findSensorRunsForPlotId(Long plotId) {
        startTransaction();
        Query query = getEntityManager().createQuery(
                "select s from SensorRunBO s "
                + "where s.plotBO.plotId = :plotId ");
        query.setParameter("plotId", plotId);
        return query.getResultList();
    }
}
